home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Utilities Professional 1-1500
/
Utilities Professional 1-1500 (1994)(WPD)[!].iso
/
12511500
/
var1450.dms
/
var1450.adf
/
NarratorDevice
/
NarratorDevice.doc
< prev
next >
Wrap
Text File
|
1992-04-27
|
43KB
|
1,215 lines
5 NARRATOR DEVICE
5.1 INTRODUCTION
The Amiga can not only produce crisp clear four channel stereo
sound, it can even speak. The operating system was designed so
this unique feature easily and efficiently can be used. With
very little effort you can let the Amiga read information.
Sadly very few programs use this extremely useful feature.
Although artificial speech sounds a bit monotone and
uninteresting it can be used in many situations to transfer
information from the computer to the user.
Imagine if the user has to check hundreds of different numbers
on the screen when the original numbers are on papers. This
very boring and slow task can be made much easier if the Amiga
automatically reads the values and the user only has to look at
the paper.
5.2 ARTIFICIAL SPEECH
Imitating human speech is a very difficult process. Even the
most sophisticated super computers can still not manage to
produce all the different intonations and sounds as a real
human voice can. The Amiga's synthesized speech mechanism is
not at all as powerful as what these super computers have, but
compared to other home computers, it is outstanding.
Producing artificial speech on the Amiga can be divided into
two steps:
1. First we have to convert the text we want to be read into
phonetical text. Phonetical text is written as the words
should sound and not as how the words are spelled.
If you open a dictionary you will notice that there exist
a specification on how every word should be pronounced.
The special symbols used to tell you how the word should
sound are usually called "the phonetic symbols", and are
defined in the beginning or end of the dictionary. The
pronunciation of all words can be constructed with help
of these symbols.
On the Amiga we use the same technique. However, since the
real phonetical symbols are very strange and can not be
typed with a normal keyboard, we use a special
"computerised" version of these symbols See next section
for a complete list of these phonetical symbols.
A great news is that you do not have to write the text
with the phonetical language. You can instead use a
special function called Translate() which can be found in
a the "Translator Library". This function can
automatically convert english text into phonetics.
2. After you have created a string with the phonetical text
you send it to the "Narrator Device" which will transform
the phonetical symbols into sound, and the text is read.
5.2.1 PHONETIC SYMBOLS
The phonetical symbols used by the dictionaries can sadly not
be used on normal computers since they use very strange
signs that can not be reproduced on normal keyboards. Instead
we have to use a special computerized set phonetical symbols
which is defined like this:
Sound Phoeme Example Type
---------------------------------
A AE Hat Vowel
AO Talk Vowel
EY Page Diphthong (A diphtong is a union
--------------------------------- of two vowel sounds or
B B Bad Consonant vowel letters.)
---------------------------------
C CH Chin Consonant
K Car Consonant
---------------------------------
D D Did Consonant
---------------------------------
E EH Ten Vowel
IY Feet Vowel
---------------------------------
F F Four Consonant
---------------------------------
G G Got Consonant
---------------------------------
H /H How Consonant
---------------------------------
I AY Five Diphthong
ER Fur Vowel
IH Sit Vowel
---------------------------------
J J Yes Consonant
---------------------------------
K K Cat Consonant
---------------------------------
L L Leg Consonant
---------------------------------
M M Man Consonant
---------------------------------
N N No Consonant
NX Sing Consonant
---------------------------------
O AA Got Vowel
AW Now Diphthong
AX About Vowel
IX Solid Vowel
OH Saw Vowel
OW Home Diphthong
OY Join Diphthong
UH Put Vowel
---------------------------------
P P Pen Consonant
---------------------------------
Q K Cat Consonant
---------------------------------
R R Red Consonant
---------------------------------
S S Sit Consonant
SH She Consonant
---------------------------------
T DH Then Consonant
T Tea Consonant
TH Thin Consonant
---------------------------------
U AH Cup Vowel
UW Too Diphthong
---------------------------------
V V Voice Consonant
---------------------------------
W W Wet Consonant
---------------------------------
X /C Loch Consonant
---------------------------------
Y Y Yellow Consonant
---------------------------------
Z Z Zoo Consonant
ZH Vision Consonant
---------------------------------
For example, the word "hello" can phonetically be written as
"/HEHLOW", and the word "house" as "/HAWZ".
5.2.2 INTONATION
When you are speeking you do not use the same voice all the
time. Some parts of the words or sentences are stressed by
using a higher voice, were other parts are prounounced more
softly.
For example, the letter "e" in the word "hello" is usually
prounounced in a higher voice. By stressing some words the text
will sound more interesting, and if used with care it can be a
very effective way to get the user's attention.
Futhermore, by stressing different parts of a sentence the
whole meaning of the sentence can change quite dramatically.
Take the sentence "I will go home" for example. By stressing
different words the user will interpretate the meaning
differently. With these four words we can actually get five
rather different meanings:
1. If no stressin is used, the meaning is simply that the
person is going home.
2. By stressing the first word "I" it now sounds like the
person him/her self is going home but the other persons
(if there are any) may do something differently.
3. By stressing the second word "will" it now sounds like
the persons is really determined and that he/she really
wants to go home.
4. If the person stresses the third word "go" he/she means
that he/she will walk home, although the word "go" can
mean travelling by car, train etc.
5. Finally, if the last word is stressed the person wants
to go to his/her home, and not any other place.
To stress parts of a word you can put a single digit directly
after the phonem which should use a higher intonation. The
digit 0 means no special intonation, while the higher digit
the more stressed will the phonem be. The digit 9 should be
used when you want maximum intonation.
For example, the stress the letter "e" in the word "hello" the
phonetically string should be written as "/HEH4LOW". The
phonem "EH" will then be medium stressed.
5.2.3 PUNCTATION AND SPECIAL SYMBOLS
There exist some symbols which are used to help the reader to
use the righ intonations. The most commonly used symbole is the
puncation ".", but others are the question mark "?", the comma
",", dash "-", etc... Most of these common punctations are
understod by the Amiga, and will automatically alter the
intonation and speed correctly.
Here are the symbols you may use:
Type Symbol How it affects the intonation
------------------------------------------------------------
Punctation . This will make the intonation of the
last word a bit softer, and there will
be a long pause before any following
words are read.
Question mark ? This will cause the Amiga to increase
the intonation of the last word so it
sounds like a question, and it will be
followed by long pause before any
following words are read.
Comma , The comma is used to divide a sentense
into several parts, but putting a small
pause before the following text.
Dash - The dash can be used as the comma, and
works much in the same way. There exist
a lot of confusing rules on when the
dash and when the comma should be used,
but I do not intead to expalin it here.
(Mainly because I do not know anything
about these rules, sadly. I use what
looks best, which I think is an
excellent rule.)
Parentheses ( ) Parantheses are used to explain
something which is not so important that
it has to be in the main sentense, but
is can still be important for the
reader. The text inside parantheses
starts off by using less intonation than
the rest of the sentense.
5.2.4 VOLUME
Although the intonation is altering the volumne a little, most
of the text will be read with the same volume. You can however
tell the narrator device to use a completely different volume,
and this can be very effective. If you normally use a soft
voice, but suddenly increase it to the maximum the user will
not miss that part.
Producing the correct sound can take some time, but once you
have mastered it, the text will sound rather smooth and,
although a bit computerized, rather human.
5.3 CONVERT TEXT INTO PHONETIC SYMBOLS
Converting text into phonetical symbols does not have to be a
hard task. If you are going to read an English text (or
American, Irish, Scottish etc...) you can use the Translate()
function. It will automatically convert the text into (almost)
correct phonetics which can be used.
The Translate() function is located in the "Translator" library
which must be opened before you can use the function. The
Translate() function is actually the only function in this
library.
5.3.1 OPEN THE TRANSLATOR LIBRARY
To open the translator library you declare a global pointer
to it which must be called "TranslatorBase", and you then call
the function OpenLibrary() to open it.
Synopsis: library_ptr = OpenLibrary( name, revision );
library_ptr: (struct Library *) If the function could open
the library it returns a pointer to it, else
NULL is returned. The translator library is
located on the system disk, and is therefore
loaded when needed. If some other program
already has opened it has already been loaded
into memory and a pointer is immediately
returned to it.
Be careful to close the library before your
program terminates. If you forget it, the
library will remain in the memory until the
computer is switched off.
name: (char *) Pointer to a string containing the
name of the library you want to open. When
you open the translator library, the name
should be "translator.library".
revision: (long) This value tells the Amiga which oldest
version may be used. If there exist a library
with the same or higher value the function will
successfully open the library, else NULL is
returned. If you simply want any version, set
this field to zero.
Here is an example: (Remember that the pointer to the
translator library must ALWAYS be called "TranslatorBase"!)
/* Pointer to the translator library: */
struct Library *TranslatorBase;
/* Open the library: */
TranslatorBase = OpenLibrary( "translator.library", 0 );
/* Have we successfully opened it? */
if( !TranslatorBase )
clean_up( "Could not open the translator library!" );
5.3.2 TRANSLATE TEXT
Once you have opened the translator device you may start to
use the Translate() function. This function will work best
with English sentences, but can be used for many other
languages if you afterwards do some small changes of the
phonetic text.
Synopsis: char_left = Translate( in, in_len, out, out_len );
char_left: (long) If not all phonetic text could fit in the
out string the function will automatically only
translate the words which will fit. (It will not
stop in the middle of a word.) If all words could
be translated zero is returned, else a negative
value is returned. This value tells us how many
letters of the "in" string have been translated.
With this value we can then call the function
again and translate the remaining text.
Note that the number is negative.
in: (char *) Pointer to the (English) text string
which should be read.
in_len: (long) The length of the (English) text string.
out: (char *) Pointer to a string where the phonetic
text string will be stored. (The "in" string is
converted and copied to the "out" string.)
out_len: (long) The length of the phonetic (out) string.
Here is an example on how to translate a string. The nice thing
is that we can translate strings of any size. If the translated
string does not fit in the buffer, we divide it up into smaller
parts.
/* Translated buffer size: */
#define PHONETIC_BUFFER_SIZE 50
/* Number of characters that were translated, or */
/* zero if all characters were translated: */
int char_translated;
/* This variable contains the current position */
/* in the string which is translated: */
int current_position;
/* The original string: */
char *original_string = "The Amiga C Encyclopedia!";
/* The phonetic string: */
char phonetic_string[ PHONETIC_BUFFER_SIZE ];
/* Open the translator library: */
TranslatorBase = (struct Library *)
OpenLibrary( "translator.library", 0 );
/* Have we successfully opened the library? */
if( !TranslatorBase )
clean_up( "Could not open the translator library!" );
/* Start with the first character in the original string: */
current_position = 0;
/* Translate (parts of) our string into phonetics: */
char_translated =
Translate( original_string,
strlen( original_string ),
phonetic_string,
PHONETIC_BUFFER_SIZE );
/* Print the translated part: */
printf( "%s\n", phonetic_string );
/* As long as Translate() does not return zero we stay */
/* in this while loop and continues to translate the */
/* original string into phonetics: */
while( char_translated )
{
/* Increase the current position in the original string: */
/* (Remember that "char_translated" variable is negative, */
/* and we must therefore use the "-=" operator and not */
/* the "+=" to increase the current position.[-- = +]) */
current_position -= char_translated;
/* Translate the following part our string into phonetics: */
/* (Note that when we put brackets after a string we we */
/* get the character at the specified position, but since */
/* we want the address of that position we also have to */
/* put the pointer "&" sign in front of the string.) */
char_translated =
Translate( &original_string[ current_position],
strlen( &original_string[ current_position] ),
phonetic_string,
PHONETIC_BUFFER_SIZE );
/* Print the translated part: */
printf( "%s\n", phonetic_string );
}
/* All words have now been translated! */
5.3.3 CLOSE THE TRANSLATOR LIBRARY
Before your program terminates it must close the translator
library. This is especially important with this library since
it is loaded from the system disk when opened, and if you do
not close it a lot of memory is wasted. Be careful about this.
As I have said many times, make sure that your program does not
only terminate nicely when it has reached the end. It must also
manage to terminate nicely if it suddenly has to quit because of
some error. If you look at the examples which accompanies this
manual you will see that most of them use a function called
"clean_up()". The idea with this function is that it examines
all resources, and if it finds out that something has been
opened or allocated it closes it before the program terminates.
Because it examines all the resources before it attempts to
close or deallocate them the function can always be called and
it will clean up everything, even if you only have opened and
allocated some resources. Try to use something similar in your
own programs.
5.4 READ PHONETIC SYMBOLS
The translator library and the function Translate() are only
used to convert normal (English) text into phonetical strings.
These phonetical strings can then be read by the Amiga, but to
do this you have to use another resource on the Amiga which is
the famous "Narrator Device". (Tataaa, trumpets and drums!)
The narrator device is very straight forward and easy to use.
As always you use request blocks to send your commands, and the
device will reply by sending messages to a reply (message)
port. The device should of course first be opened by calling
the OpenDevice() function. When your program wants to terminate
the device and message port are closed and the request block
is deallocated.
5.4.1 NARRATOR REQUEST BLOCK
When you are using the narrator device you have to use an
extended request block called "narrator_rb" which is defined
like this: (Defined in header file "devices/narrator.h".)
struct narrator_rb
{
struct IOStdReq message;
UWORD rate;
UWORD pitch;
UWORD mode;
UWORD sex;
UBYTE *ch_masks;
UWORD nm_masks;
UWORD volume;
UWORD sampfreq;
UBYTE mouths;
UBYTE chanmask;
UBYTE numchan;
UBYTE pad;
};
message: The top part of this request block consists of the
standard input output request block. This IOStdReq
block is explained below.
rate: The speaking rate in words per minute. The default
rate (DEFRATE) has been defined as 150 words per
minute. The maximum speed (MAXRATE) is 400 words and
the minimum speed (MINRATE) is 40 words per minute.
pitch: The voice's pitch. This is the medium pitch value.
Stressed words are spoken with a higher pitch, while
soft words are spoken with a lower pitch. By
changing this value the whole voice is affected.
The default pitch (DEFPITCH) is 110. The maximum
pitch (MAXPITCH) is 320 and the minimum pitch is 65.
mode: This value will affect the way the text is read. If
you set the flag "NATURALF0" the voice will go up
and down as the text is read. On the other hand, if
you set the flag "ROBOTICF0" the voice will be very
dull and does not change as the text is read.
sex: If the flag "MALE" is set a dark male voice will be
used. If you instead set the flag "FEMALE" a lighter
and sharper voice will be used. (Do not ask me why
they did not define it as "BITCH".)
*ch_masks: This field should be given a pointer to an
"allocation array" which specifies which audio
channels that should be used.
There exist four sound channels which can be used.
The first and last sound channel is connected to the
left audio port, while the second and third is
connected to the right audio port.
To specify which channels you want to use you use
a value where the first bit represents the first
sound channel, the second bit the second sound
channel and so on...
Bit: 3 2 1 0
-------------------------------------
Value: 8 4 2 1
Channel: 3 2 1 0
Audio port: Left Right Right Left
To reserve the first and last channel (both using
the left sound channel) you would use the value 9
(1001[bin] = 9[dec]). To reserve the second and
third channel (both using the right sound channel)
you would use the value 6 (0110[bin] = 6[dec]).
To reserve all four sound channels set the value to
15 (1111[bin] = 15[dec]).
The allocation array should consist of all the
channel combinations you like. The narrator device
will first try to reserve the channels specified by
the first value. If it could not get these channels,
it tries the next values until if finds a
combination it could reserve or it reaches the end
of the allocation array and the operation fails.
For example. If you want that the text should be
read in stereo you have to use one audio channel
from the right and one from the left port. There
exist four possible combinations which satisfy this,
and the "allocation array" should therefore look
like this:
UBYTE allocation_array = { 3, 5, 10,12 };
Dec Bin Description
---------------------------------------------------
3 0011 First left and first right channel.
5 0101 First left and second right channel.
10 1010 Second left and first right channel.
12 1100 Second left and second right channel.
nm_masks: This field should be given a number which tells the
narrator device how many values there are in the
allocation array.
volume: The volume of the voice. The default volume (DEFVOL)
is the same as the maximum volume (MAXVOL) which is
defined as 64. The minimum volume (MINVOL) is 0
which is the same thing as silent.
I myself does not like that the maximum volume is
often used. It would be much better if everyone were
using the medium volume as the default value. The
user will then turn up the volume on his/her stereo
to compensate for the lower volume. The advantage is
that if you now suddenly play a very loud sound or
use a very loud voice it will also be very loud.
Imagine a game which is normally using a sound level
of 32. The user is sitting alone in his/her own room,
and it is very late. Imagine what would happen if a
monster would appear in the the game and at the same
time the volume was increased to maximum. It will
undoubtedly be a nice (hmmm) surprise for the user.
sampfreq: This field specifies which frequency should be used.
The default frequency (DEFFREQ) is 22200. The
maximum value (MAXFREQ) is 28000 and the minimum
value (MINFREQ) is 5000.
mouths: Normally this field should be set to zero. However,
of you are going to use the "mouth" request, as
explained below, you should set a non zero value.
chanmask: This field contains the value which was used of the
allocation array to reserve the audio channel(s).
This field is used by the system, but if you like
you may examine it, but you may never change it.
numchan: Number of channels used. This field is also used by
the system, but if you like you may examine it, but
you may never change it.
pad: This field is not used, and should therefore never
be used.
The IOStdReq structure (which is a part of the narrator_rb
structure) is defined in the "exec/io.h" header file like this:
(Only some parts of this structure will be used, so you do not
have bother too much about it.)
struct IOStdReq
{
struct Message io_Message;
struct Device *io_Device;
struct Unit *io_Unit;
UWORD io_Command;
UBYTE io_Flags;
BYTE io_Error;
ULONG io_Actual;
ULONG io_Length;
APTR io_Data;
ULONG io_Offset;
};
io_Message: The top part consists of a Message structure which
will be sent to us when the request has been
completed (successfully or not). This message
structure will automatically be given a pointer to
the reply port when the narrator_rb structure is
created, and the priority will automatically be set
when used, so we do not have to bother too much
about it.
io_Device: This field will automatically be initialized when
you use this request block together with an
OpenDevice() function call. It is simply a pointer
to the device which this request block is made for.
io_Unit: This field will automatically be initialized when
you send the request block to the narrator device.
io_Command: The following commands are accepted by the narrator
device, and may be used: (Will be explained below.)
CMD_WRITE Start to speak.
CMD_STOP Stop all speech requests.
CMD_START Start to speak again.
CMD_FLUSH Remove all queued requests.
CMD_RESET Reset the narrator device.
io_Flags: No flags are used.
io_Error: If the request can not successfully be executed by
the narrator device, it will be returned with one
of the following error flags:
ND_NoMem Not enough memory for the request.
ND_NoAudLib Can not open the audio device.
ND_MakeBad Can not execute the MakeLibrary()
function.
ND_UnitErr Wrong unit number (not 0).
ND_CantAlloc Can not find any free audio channels
to reserve.
ND_Unimpl Not a valid command.
ND_NoWrite Nothing is being read, so you can
not read any "mouth" values.
ND_Expunged Wrong expunge value.
ND_PhonErr Wrong phonetic code.
ND_RateErr Too large or small rate value
ND_PitchErr Too large or small pitch value
ND_SexErr Wrong sex number used.
ND_ModeErr Wrong mode value used.
ND_FreqErr Too high or low frequency value.
ND_VolErr Too high or low volume value.
io_Actual: Used by the system.
io_Length: The number of characters in the phonetic string
that should be read.
io_Data: Pointer to the phonetic sting.
io_Offset: Used by the system.
5.4.2 OPEN THE NARRATOR DEVICE
To open the narrator device does not differ from opening other
devices. First you have to create a message port to which the
device can send messages to you. Secondly you have to create
one or more narrator_rb structures by using the CreateExtIO()
function. Finally you can open the narrator device with help
of one of the request blocks.
1. Open a message port. Since it is only our task and the
narrator device that will use the message port, we do not
need to make it "public", therefore no name. Priority
should as usual be set to 0, normal priority.
struct MsgPort *replymp;
replymp = (struct MsgPort *)
CreatePort( NULL, 0 );
if( !replymp )
clean_up( "Could not create the reply port!" );
2. Allocate a request block of type narrator_rb structure.
The narrator_rb structure is an extended version of the
normal request block, and should therefore be allocated
with help of the CreateExtIO() function with the size
set to sizeof( struct narrator_rb ).
struct narrator_rb *narrator_req;
narrator_req = (struct narrator_rb *)
CreateExtIO( replymp, sizeof( struct narrator_rb ) );
if( !narrator_req )
clean_up( "Not enough memory!" );
3. Once the message port and the request block have
successfully been created you can "open" (gain access to)
the narrator device. The default values will now also be
set in the request block.
error = OpenDevice( "narrator.device", 0, narrator_req, 0 );
if( error )
clean_up( "Could not open the Narrator Device!" );
5.4.3 READ PHONETIC TEXT
Once you have successfully opened the narrator device you may
start to use the narrator device. The fields of the request
block with which the narrator device was opened with have
automatically been given the default values:
1. The rate is set to 150 words per minute.
2. The pitch is set to 110 Hz.
3. The mode is set to normal (living) voice.
4. The sex field is set to man.
5. The sampfreq is set to 22200.
6. The volume is set to 64.
The following fields must be initialized by yourself:
1. You must give the "io_Data" field of the IOStdReq
structure a pointer to the string of phonetic words that
should be read.
2. Give the "io_Length" field of the IOStdReq structure the
number of translated characters that should be read.
You can use the function strlen() to find out how long
the translated string is.
3. You have to give the "ch_masks" field a pointer to the
allocation array that should be used.
4. Give the "nm_masks" field the number of values which exist
in the allocation array.
5. Finally you should give the "io_Command" field of the
IOStdReq structure the "CMD_WRITE" flag. (We are going to
send [write] text to the narrator device.)
Here is an example:
/* The text should be read in stereo: */
UBYTE allocation_array[]=
{
LEFT0F|RIGHT0F, /* First left and first right channel. */
LEFT0F|RIGHT1F, /* First left and second right channel. */
LEFT1F|RIGHT0F, /* Second left and first right channel. */
LEFT1F|RIGHT1F /* Second left and second right channel. */
};
/* The phonetic string: */
char *phonetic_string = "/HEH4LOW";
/* Message port opened... */
/* "narrator_rb" created... */
/* Narrator device opened... */
/* Set our requirements: */
/* 1. Give it a pointer to the phonetic string: */
narrator_req->message.io_Data = (APTR) phonetic_string;
/* 2. Set the length of the phonetic string: */
narrator_req->message.io_Length = strlen( phonetic_string );
/* 3. Desired channel combinations: */
narrator_req->ch_masks = allocation_array;
/* 4. Size of the allocation array: */
narrator_req->nm_masks = sizeof( allocation_array );
/* 5. Send (write) the text to the device: */
narrator_req->message.io_Command = CMD_WRITE;
You may of course also change the other values if you do not
want to use the default ones.
After the request has been initialized can it be sent to the
narrator device which will read the phonetic string. To send
request you can either use the synchronous command DoIO() or
the asynchronous command SendIO().
The DoIO() function will send the request to the device and
put your program to sleep. When the device has finished your
request it is returned and your program wakes up. If something
failed will DoIO() return an error number and a copy of their
value will also be stored in the "io_Error" field of the
request block.
The SendIO() function should be used when you want to continue
to do something while the narrator device is reading the text.
After you have called SendIO() you can put your task to sleep
by either using the WaitIO() or Wait() function. If you want
to check if the request has been completed you can use the
CheckIO() function.
All these functions have been described in chapter "Devices",
and will therefore not be repeated here.
An example on a synchronous call: (Your program is put to sleep
while the narrator device completes your request.)
BYTE error;
/* ... */
/* Read the text: */
error = DoIO( narrator_req );
/* Were there any errors? */
if( error )
clean_up( "Problems with reading the text!" );
An example on an asynchronous call: (Your program continues to
run while the text is read.)
/* Start to read: */
SendIO( narrator_req );
while( !CheckIO( narrator_req ) )
{
/* Do something... */
}
/* Collect message at the reply port... */
/* Were there any errors? */
if( narrator_req->message.io_Error )
clean_up( "Problems with reading the text!" );
5.4.4 USING SEVERAL REQUEST BLOCKS
If you have created several request blocks will only the one
which you opened the narrator device with be correctly
preinitialized. These structures which have not been initialized
must be prepared before they may be used. The simplest solution
is actually to copy the whole structure, because you will then
be sure that no values are forgotten.
Here is an example:
/* Source and destination pointers: */
BYTE *first_ptr;
BYTE *second_ptr;
/* Allocate request blocks, open narrator device, etc... */
/* (The two request blocks are called "first_narrator_req" */
/* and "second_narrator_req".) */
/* Copy the first request block to the second one: */
/* Get the start addresses of both request blocks: */
first_ptr = (BYTE *) first_narrator_req;
second_ptr = (BYTE *) second_narrator_req;
/* Copy byte by byte: */
for( loop = 0; loop < sizeof( struct narrator_rb ); loop++ )
{
/* Copy: */
*second_ptr = *first_ptr;
/* Next byte: */
first_ptr++;
second_ptr++;
}
5.4.5 CLEAN UP
When your program terminates you have to clean up and free all
allocated resources. This is what has to be done:
1. Remove any messages still left at the message (reply) port.
2. Close the message (reply) port(s).
3. Close the narrator device.
4. Deallocate the request blocks.
5.4.5.1 REMOVE ALL MESSAGES
Before you close anything you should first remove all messages
from the message (reply) port. A short while loop that removes
the messages until it can not find any more is a simple but
effective solution.
while( GetMsg( replymp ) )
printf( "Collected a message at the reply port.\n" );
5.4.5.2 CLOSE MESSAGE PORT
After all messages have been removed from the port you may close
it by calling the DeletePort() function. Note that you should
never try to close a message port you have not opened.
DeletePort( replymp);
5.4.5.3 CLOSE THE NARRATOR DEVICE
The narrator device itself must of course also be closed if you
have opened it. Note that you should never try to close a
device you have not opened.
CloseDevice( narrator_req );
5.4.4 DEALLOCATE THE REQUEST BLOCKS
Finally you should deallocate all request blocks you have
created. Use the DeleteExtIO() function. The size should be set
to the size of a narrator_rb structure. Just remember to close
the narrator device before you deallocate the request block(s).
DeleteExtIO( narrator_req, sizeof( struct narrator_rb ) );
5.5 THE NARRATOR'S MOUTH
The narrator device offers a rather unique but sometimes useful
feature. The narrator device can be used to draw a mouth as the
text is read. It may sound a bit strange, but if you are using
animations this can be very helpful.
The narrator device will tell us how wide and high the mouth
should be, and with this we can easily construct a simple
mouth.
5.5.1 MOUTH REQUEST BLOCK
To use this "mouth" feature you have to use a special request
block which is called "mouth_rb", and looks like this: (Defined
in header file "devices/narrator.h".)
struct mouth_rb
{
struct narrator_rb voice;
UBYTE width;
UBYTE height;
UBYTE shape;
UBYTE pad;
};
voice: The top part of the mouth block consists of a
"narrator_rb" structure. When you create this structure
the message port will automatically be initialized.
The following fields must however be initialized by
yourself:
1. You must copy the "io_Device" pointer from the
request which is reading the text.
2. The "io_Unit" number must also be copied.
3. The "io_Error" field should be set to zero.
4. Finally you should set the command "CMD_READ"
in the "io_Command" field.
After you have sent this request block and it is
returned you can examine the "io_Error" field to
check if there were any problems. See above for a
complete list of error flags.
When you are using this request, it will be returned to
you if one of the following thing has happened:
1. The size of the mouth has changed.
2. Something went wrong, and the "io_Error" field will
contain an error flag. If you have received the
"ND_NoWrite" the Amiga has stopped talking, and
you should not try to draw any mouth any more.
width: After you have successfully executed this request you
can examine this field to check how wide the mouth
should be. The request block will only be returned when
the size of the mouth has changed, or the Amiga has
stopped talking.
height: This field will contain the height of the mouth.
shape: This field may only be used by the system, and should
never be changed.
pad: No used, and should not be used.
5.5.2 CREATE A MOUTH REQUEST BLOCK
Since the size of the mouth_rb is larger than the standard
sized request block you have to use the CreateExtIO() function
to allocate and preinitialize the request block. Normally it
is easiest to connect this structure to the same message port
as the other request blocks are connected to. You can of course
use a separate message port if you want.
struct mouth_rb *mouth_req;
mouth_req = (struct mouth_rb *)
CreateExtIO( replymp, sizeof( struct mouth_rb ) );
if( !mouth_req )
clean_up( "Not enough memory!" );
5.5.3 PREPARE THE MOUTH REQUEST BLOCK
After you have created the mouth_rb structure you have to copy
some values, as explained above, from the request block which
will read the text. You must also set some values yourself.
Here is an example on how to initilaize the mouth request:
/* Set the mouth width and height to zero: */
mouth_req->width = 0;
mouth_req->height = 0;
/* Give the mouth request a pointer to the narrator device: */
mouth_req->voice.message.io_Device = narrator_req->message.io_Device;
/* Give the mouth request the current unit number: */
mouth_req->voice.message.io_Unit = narrator_req->message.io_Unit;
/* No error number (so far): */
mouth_req->voice.message.io_Error = 0;
/* The mouth request should look at (read) the request */
/* which is currently talking: */
mouth_req->voice.message.io_Command = CMD_READ;
The mouth_rb request block can now be used.
5.5.4 GET THE SIZE OF THE MOUTH
The mouth request block should be sent to the narrator device
after you have sent a normal read request. If the device is
currently not reading any text the mouth request will
immediately be returned with the error flag "ND_NoWrite" set.
If the device is reading some text the mouth request will first
be returned when the mouth has changed size, or if the narrator
device has reached the end of the text string. If the device
reached the end of the string and stops talking the mouth
request will be returned with the error flag "ND_NoWrite" set.
However, if the request is returned and no error flag is set,
the mouth has changed size and should be redrawn.
Since your program must be able to work while the narrator
device is reading the text you have to use the asynchronous
command SendIO() to start the read request. Here is an example
on how to use the narrator's mouth:
/* TRUE as long the Amiga is reading text: */
BOOL still_talking;
/* ... */
/* Start to read: (The read request must be sent by */
/* the asynchronous command SendIO().) */
SendIO( narrator_req );
/* The Amiga is reading the text: */
still_talking = TRUE;
/* As long as the Amiga is reading the text */
/* we stay in the while loop: */
while( still_talking )
{
/* Send the mouth request to the narrator device. The request */
/* will be returned when the mouth width and/or height have */
/* changed, or the device has stopped reading the text: */
DoIO( mouth_req );
/* Has the device stopped reading the text: */
if( mouth_req->voice.message.io_Error == ND_NoWrite )
still_talking = FALSE;
else
{
/* No, the device is still reading. The mouth must have */
/* changed, so we better redraw it: */
/* Draw the new mouth... */
}
}
/* We know that the Amiga has stopped speaking, */
/* and hence we do not have to wait for the */
/* narrator to finish reading. We should however */
/* check if we have successfully read the text: */
if( narrator_req->message.io_Error )
clean_up( "Error while reading!" );
5.6 EXAMPLES
Example 1
This very simple example demonstrates how to open the
translator library, translate a string, and finally close
the library before the program terminates.
Example 2
This example demonstrates how you can use a while loop to
translate parts of a string until the whole string has been
translated.
Example 3
This example demonstrates how to translate a string into a
phonetical string which is then read by the narrator device.
Example 4
This example very similar to the previous one, but this time
are we using a different voice. By altering the rate, pitch,
mode, sex and volume, you can produce very different sounds.
Example 5
This example demonstrates how you can let the Amiga read
small stories. By altering the rate, pitch, mode, sex and
volume parameters it can sound like several persons are
talking. It can also be used to express emotions and stress
important parts of the text.
This example is using some home made functions which makes
life a little bit easier. If you have to read a lot of text I
recommend you to use special functions like these. It will
then be much easier to write (and read) the program code.
Example6
This example demonstrates how to use the mouth request block
to draw a talking mouth.